/*********************************************************************************
 *      Copyright:  (C) 2023 Lin XinCheng
 *                  All rights reserved.
 *
 *       Filename:  ds18b20.c
 *    Description:  This file 
 *                 
 *        Version:  1.0.0(2023年07月21日)
 *         Author:  Lin XinCheng <1481155734@qq.com>
 *      ChangeLog:  1, Release initial version on "2023年07月21日 14时32分40秒"
 *                 
 ********************************************************************************/

#include <linux/module.h>
#include <linux/init.h>
#include <linux/fs.h>		//file_operations，用于联系系统调用和驱动程序
#include <linux/errno.h>	//ENODEV，ENOMEM存放的头文件
#include <linux/kernel.h>	// printk()，内核打印函数
#include <linux/device.h>	//用于生成设备节点的函数头文件
#include <linux/gpio.h>		//gpio相关函数
#include <linux/gpio/consumer.h>	// gpiod相关函数
#include <linux/of_gpio.h>	//gpio子系统相关函数
#include <linux/uaccess.h>	//copy_to_user函数头文件
#include <linux/timer.h>	//定时器相关
#include <linux/delay.h>	//延时函数头文件
#include <linux/cdev.h>		//cdev相关函数
#include <linux/platform_device.h>	//platform相关结构体
#include <linux/delay.h>

#define DEV_NAME	"w1_ds18b20"	//最后在/dev路径下的设备名称，应用层open的字符串名
#define DEV_CNT		1	

#ifndef	DEV_MAJOR
#define DEV_MAJOR	0
#endif

#define LOW		0
#define HIGH	1

/*  DS18B20 ROM命令  */
#define Read_ROM  0x33
#define SKIP_ROM  0xCC

/*  DS18B20 FUNC命令 */
#define Convert_T   0x44   // initiates a single temperature conversion.
#define Read_Data	0xBE   // the master to read the contents of the scratchpad 

#define CRC_MODEL	0x31
#define CRC_SUCCESS 0
#define CRC_FAIL	1

static int dev_major = DEV_MAJOR;	//主设备号

/* 存放Led的私有属性 */
struct gpio_w1_priv {
	struct cdev		cdev;//cdev结构体
	struct class	*dev_class;/* 自动创建设备节点的类 */
	struct device	*dev;
	spinlock_t		lock;
};

struct gpio_desc	*w1_gpiod;

/* DS18B20 接收发送 */
#define DS18B20_In_init()       gpiod_direction_input(w1_gpiod)//设置GPIO的方向为输出，默认电平为高    
#define DS18B20_Out_init()		gpiod_direction_output(w1_gpiod, HIGH)//设置GPIO的方向为输出，默认电平为高
#define DS18B20_DQ_IN()			gpiod_get_value(w1_gpiod)
#define DS18B20_DQ_OUT(N)		gpiod_set_value(w1_gpiod,N)

/*  @description: 解析设备树，初始化w1硬件io 
*	
*	@parm : pdev-platform_device 设备指针
*	@parm :
*	@return:0 successfully,!0 failure
*/
static int parser_dt_init_w1(struct platform_device *pdev)
{
	struct device *dev = &pdev->dev; //设备指针
	struct gpio_w1_priv	*priv = NULL; //存放私有属性
	struct gpio_desc	*gpiod;		 //gpiod结构体

	//为1w私有属性分配存储空间
	priv = devm_kzalloc(dev, sizeof(struct gpio_w1_priv), GFP_KERNEL);
	if(!priv)
		return -ENOMEM;

	//获取w1的设备树节点
	gpiod = gpiod_get(dev, "w1", 0); //请求gpio，有请求一定要有释放，否则模块下一次安装将请求失败
	if(IS_ERR(gpiod))
	{
		printk("gpiod request failure\n");
		return PTR_ERR(gpiod);
	}

	/* 将gpiod传给结构体 */
	w1_gpiod = gpiod;

	dev_info(&pdev->dev, "parser dts got valid w1\n");

	platform_set_drvdata(pdev, priv); //设置私有数据，方便其他函数操作
	return 0;
}

static int w1_delay_parm = 1;

static void w1_delay(unsigned long tm)
{
	udelay(tm *w1_delay_parm);
}

/*  @description: 主机发送起始信号，主机拉低 >=480us，释放总线（主机拉高），等待15~60us
 *  @return: 0 successfully , !0 failure
 */
static int DS18B20_start(void)
{
	int rv;

	/* 主机设置为输出 */
	DS18B20_Out_init(); //设置GPIO的方向为输出，默认电平为高

	/* 主机拉低 >=480us */
	DS18B20_DQ_OUT(LOW);
	w1_delay(480);

	/* 主机拉高 15~60us */
	DS18B20_DQ_OUT(HIGH);
	w1_delay(70);

	/* 主机设置为输入 */
	DS18B20_In_init();

	/* 总线变低说明从设备发送了响应信号：60~240us */
	rv = DS18B20_DQ_IN();
	if(0 != rv )
	{
		printk("%s():%d DS18B20_no respond \n",__FUNCTION__,__LINE__);
		return -EFAULT;
	}
	printk("%s():%d DS18B20_respond \n",__FUNCTION__,__LINE__);
	w1_delay(10);

	DS18B20_Out_init();
	DS18B20_DQ_OUT(HIGH);//释放总线

	return rv;
}

/*  @description: 主机读取一个位,整个读周期最少需要60us，启动读开始信号后必须15us内读取IO电平，否则就会被上拉拉高 
 *   *
 *   * @return: uint8_t bit 
*/
static uint8_t DS18B20_readBit(void)
{
	uint8_t bit = 0;
	/* 主机再设置成输出模式准备开始读数据 */
	DS18B20_Out_init();//设置GPIO的方向为输出，默认电平为高

	 /* 主机拉低 >=1，表示开始读数据*/
	DS18B20_DQ_OUT(LOW);
	w1_delay(2);

	DS18B20_In_init();

	w1_delay(10);
	/* 判断数据是0还是1 */
	bit = DS18B20_DQ_IN();

	w1_delay(50);

	return bit;
}

/*  @description: 读取一个字节
 *   *
 *   * @return: uint8_t Bety 
*/
static uint8_t DS18B20_readBety(void)
{
	uint8_t	i,Bety = 0;
	uint8_t bit;

	for(i=0;i<8;i++)
	{
		bit = DS18B20_readBit();
		if(bit)
			Bety |= (0x01 << i);
		
		// Bety |= DS18B20_readBit();/* MSB最先读到的是最高位 */
		// Bety <<= 1;
	}
	printk("%s() :%d DS18B20_readBit %x \n",__FUNCTION__,__LINE__,Bety);
	return Bety;
}

/*  @description: ds18b20 写一个位
 *     *
 *     * @parm  : bit - 一个字节
 *     * @parm  : 
 *     * @return: 
*/
static void DS18B20_writeBit(unsigned char bit)
{
	DS18B20_Out_init();

	//判断bit
	bit = bit > 1 ? 1 : bit;
	w1_delay(50);

	DS18B20_DQ_OUT(LOW);
	w1_delay(2);
	DS18B20_DQ_OUT(bit);
	w1_delay(60);
	DS18B20_DQ_OUT(HIGH);
	w1_delay(12);
}

/*  @description: ds18b20 写一个字节
 * @parm  : bit - 一个字
 * @parm  : 
 * @return: 
 */
static void DS18B20_writeBety(uint8_t Bety)
{
	uint8_t i = 0;
	for(i=0; i<8; i++)
	{
		DS18B20_writeBit((Bety >> i)&0x01);
	}
	printk("%s():%d DS18B20_writeBety %x \n",__FUNCTION__,__LINE__,Bety);
}

/*  @description: 读取温度值
 * @parm  : struct gpio_w1_priv *priv 私有数据结构体
 * @parm  : 
 * @return: uint16_t bit 
 */
static uint16_t DS18B20_SampleData(struct gpio_w1_priv *priv)
{
	unsigned long	flags;
	uint16_t		temp = 0;
	uint8_t			temp_H = 0;
	uint8_t			temp_L = 0;
	int				rv = 0;

	spin_lock_irqsave(&priv->lock, flags);
	/* 主机发起起始信号，并且从设备回应 */
	if(0 != DS18B20_start())
	{
		printk("%s(): %d DS18B20_start err \n",__FUNCTION__,__LINE__);
		rv = -EFAULT;
		goto undo_spin_unlock;
	}

	DS18B20_writeBety(SKIP_ROM);
	DS18B20_writeBety(Convert_T);

	spin_unlock_irqrestore(&priv->lock, flags);

	msleep(750);

	spin_lock_irqsave(&priv->lock, flags);

	if( 0 != DS18B20_start())
	{
		printk("%s(): %d DS18B20_start err \n",__FUNCTION__,__LINE__);
		rv = -EFAULT;
		goto undo_spin_unlock;
	}

	DS18B20_writeBety(SKIP_ROM);
	DS18B20_writeBety(Read_Data);

	temp_L = DS18B20_readBety();
	temp_H = DS18B20_readBety();

	spin_unlock_irqrestore(&priv->lock, flags);

	// temp_H = 0xff;
	// temp_L = 0x5e;
	
	temp = (temp_H << 8) | temp_L;

	//printk("%s():%d temp_H:%x, temp_L:%x ,temp %x\n", __FUNCTION__, __LINE__, temp_H, temp_L, temp);
#if 0
	//if(temp&0x8000>0)
	/* 如果为负数，就是算补码：取反再加一, 这个地方不需要计算,因为直接就可以判断是正还是负数*/
	if(temp < 0)
	{
		temp = ~temp + 1;
	}
#endif

	return temp;

undo_spin_unlock:
	spin_unlock_irqrestore(&priv->lock, flags);
	return rv;
}

/*  @description: 打开设备*
 * @parm  : inode - 传递给驱动的inode
 * @parm  : filp - 设备文件，file结构体有个叫做private_data的成员变量
 *     一般在open的时候将private_data指向设备结构体。
 * @return: 0 successfully , !0 failure
 */
/* 字符设备操作函数集 */
static int w1_open(struct inode *inode, struct file *file)
{
	struct gpio_w1_priv *priv;

	printk("%s():%d w1_open platform driver\n",__FUNCTION__,__LINE__);
	priv = container_of(inode->i_cdev, struct gpio_w1_priv, cdev); //找到私有数据的地址
	file->private_data = priv;
	return 0;
}

/*  @description: 从设备读取文件
 *   * @parm  : filp - 设备文件，文件描述符
 *   * @parm  : buf - 返回给用户空间的数据缓冲区
 *   * @parm  : cnt - 要读取的数据长度
 *   * @parm  : offt - 相对于文件首地址的偏移
 *   * @return: 读取的字节数，负数 - 读取失败
 */
static ssize_t w1_read(struct file *filp, char __user *buf, size_t cnt, loff_t *off)
{
	int rv = 0;
	uint16_t temp = 0;

	struct gpio_w1_priv *priv = filp->private_data;

	temp = DS18B20_SampleData(priv);
	printk("%s():%d temp %x\n",__FUNCTION__,__LINE__,temp);

	rv = copy_to_user(buf, &temp, sizeof(temp));
	if(rv)
	{
		dev_err(priv->dev, "copy to user error\n");
		return -EFAULT;
	}
	else
	{
		rv = sizeof(temp);
	}
	return rv;
}

/*  @description: 关闭设备
 *
 * @parm  : inode - 传递给驱动的inode
 * @parm  : filp - 设备文件，file结构体有个私有数据可以使用
 * @return: 0 successfully , !0 failure
 */
static int w1_release(struct inode *inode, struct file *file)
{
	printk("%s():%d w1_release w1 platform driver\n",__FUNCTION__,__LINE__);
	return 0;
}

/* 字符设备操作函数集 */
static struct file_operations w1_fops = {
	.owner = THIS_MODULE,
	.open = w1_open,
	.read = w1_read,
	.release = w1_release,
};

/*  @description: sysfs - 温度属性显示函数
 * *
 * * @parm  : devp - 设备指针,创建file时候会指定dev
 * * @parm  : attr - 设备属性,创建时候传入
 * * @parm  : buf - 传出给sysfs中显示的buf
 * * @return: 显示的字节数
 * */
static ssize_t temp_show(struct device *dev, struct device_attribut *attr, const char *buf, size_t count)
{
	return count;
}

/* 声明并初始化一个 device_attribute结构体 */
//DEVICE_ATTR(temp, 0644, temp_show, temp_store);

/* probe函数实现字符设备的注册和LED灯的初始化 */
static int gpio_w1_probe(struct platform_device *pdev)
{
	struct gpio_w1_priv	*priv; //临时存放私有属性的结构体
	struct device		*dev;  //设备结构体
	dev_t				devno;	//设备的主次设备号
	int					rv = 0; 

	/* 解析设备树并初始化w1状态 */
	rv = parser_dt_init_w1(pdev);
	if( rv<0 )
		return rv;

	/* 将之前存入的私有属性。放入临时结构体中 */
	priv = platform_get_drvdata(pdev);

	/* ---------------------注册 字符设备部分-----------------*/
	/* 1.分配主次设备号，这里即支持静态指定，也至此动态申请*/
	if( 0 != dev_major )/* static */
	{
		devno = MKDEV(dev_major, 0);
		rv = register_chrdev_region(devno, 1, DEV_NAME);
	}
	else
	{
		rv = alloc_chrdev_region(&devno,0, 1, DEV_NAME);/* 动态申请字符设备号 */
		dev_major = MAJOR(devno); /* 获取主设备号 */
	}

	/* 自动分配设备号失败 */
	if(rv < 0 )
	{
		printk("%s driver can't get major %d",DEV_NAME, dev_major);
		return rv;
	}
	printk(" %s driver use major %d\n", DEV_NAME, dev_major);

	/* 2.分配cdev结构体，绑定主次设备号、fops到cdev结构体中，并注册给Linux内核 */
	priv->cdev.owner = THIS_MODULE; /* .owner这表示谁拥有你这个驱动程序 */
	cdev_init(&priv->cdev, &w1_fops); /* 初始化cdev,把fops添加进去 */
	rv = cdev_add(&priv->cdev, devno, DEV_CNT); /* 注册给内核,设备数量1个 */

	if( 0 != rv)
	{
		printk("%s driver can't register cdev:result = %d\n",DEV_NAME, rv);
		goto undo_major;
	}
	printk("%s driver can register cdev:result=%d\n",DEV_NAME, rv);

	//3.创建类，驱动中进行节点创建
	priv->dev_class = class_create(THIS_MODULE, DEV_NAME); /*  /sys/class/my_w1 创建类 */
	if(IS_ERR(priv->dev_class))
	{
		printk("%s driver create class failure\n",DEV_NAME);
		rv = -ENOMEM;
		goto undo_cdev;
	}

	//4.创建设备
	devno = MKDEV(dev_major, 0); //给每一个led设置设备号
	dev= device_create(priv->dev_class, NULL, devno, NULL, DEV_NAME); /*  /dev/my_w1 注册这个设备节点 */
	if(IS_ERR(priv->dev_class))
	{
		rv = -ENOMEM;	//返回错误码,应用空间strerror查看
		goto undo_class;
	}

	//5.初始化自旋锁
	spin_lock_init(&priv->lock);

	//6. 创建sys 属性 在platform下
	if(device_create_file(dev, &dev_attr_temp))
	{
		rv = -ENOMEM;
		goto undo_device;
	}

	priv->dev = dev; /* 把dev赋给priv结构体 */

	//7. 保存私有数据
	platform_set_drvdata(pdev, priv);
	dev_set_drvdata(priv->dev, priv);
	dev_info(priv->dev, "%s driver probe okay.\n",DEV_NAME);

	dev_info(&pdev->dev, "%s driver probe okat.\n",DEV_NAME);

undo_device:
	device_destroy(priv->dev_class, devno); //注销设备号
undo_class:
	class_destroy(priv->dev_class);
undo_cdev:
	cdev_del(&priv->cdev);
undo_major:
	unregister_chrdev_region(devno, DEV_CNT);

	return rv;
}

/*  @description: 设备卸载时候执行
 * *
 * * @parm  : pdev - 设备指针
 * * @return: 0 successfully , !0 failure
 * */
static int gpio_w1_remove(struct platform_device *pdev)
{
	struct gpio_w1_priv *priv = platform_get_drvdata(pdev); //临时存放私有属性的结构体
	dev_t devno = MKDEV(dev_major, 0);

	//删除sys中的属性
	device_remove_file(priv->dev , &dev_attr_temp);

	device_destroy(priv->dev_class, devno); //注销每一个设备号

	class_destroy(priv->dev_class);//注销类
	cdev_del(&priv->cdev);//删除cdev
	unregister_chrdev_region(MKDEV(dev_major, 0),DEV_CNT); //释放设备号

	gpiod_set_value(w1_gpiod, 0);//低电平关灯
	gpiod_put(w1_gpiod);//释放gpio

	devm_kfree(&pdev->dev, priv); //释放堆

	printk("gpio w1 driver removed.\n");
	return 0;
}

/*  @description: 设备停止执行的函数
 * *
 * * @parm  : pdev - 设备指针
 * * @return: void
 * */
static void imx_w1_shutdown(struct platform_device *pdev)
{
	struct gpio_w1_priv *priv = platform_get_drvdata(pdev);
	
	gpiod_set_value(w1_gpiod, 0);
}

/* ------------------第一部分---------------- */
static  const struct of_device_id w1_match_table[] = {
	{.compatible = "ds18b20-gpio"},
	{},
};

MODULE_DEVICE_TABLE(of, w1_match_table);

/* 定义平台驱动结构体 */
static struct platform_driver gpio_w1_driver = {
	.probe	=	gpio_w1_probe,//驱动安装时候会执行的钩子函数
	.remove	=	gpio_w1_remove,//驱动卸载程序
	.shutdown	=	imx_w1_shutdown,//设备停止时
	.driver	={
		.name = "ds18b20-gpios",	//无设备树时，用于设备和驱动间匹配
		.owner = THIS_MODULE,
		.of_match_table	=	w1_match_table,//有设备树后，利用设备树匹配表
	},
};

/* ------------------第二部分---------------- */
static int __init platdrv_gpio_w1_init(void)
{
	int rv	=	0;
	rv	=	platform_driver_register(&gpio_w1_driver); //注册platform的led驱动   
	if (rv)
	{
		printk(KERN_ERR "%s:%d:Can't register platform driver %d\n",__FUNCTION__,__LINE__, rv);
		return rv;
	}
	printk("Regist w1 Platform Driver successfully!\n");
	return 0;
}

/* ------------------第三部分---------------- */
//驱动注销函数
static void __exit platdrv_gpio_w1_exit(void)
{
	printk("%s():%d remove w1 platform driver\n",__FUNCTION__,__LINE__);
	platform_driver_unregister(&gpio_w1_driver); //卸载驱动
}

module_init(platdrv_gpio_w1_init);
module_exit(platdrv_gpio_w1_exit);

MODULE_AUTHOR("Linxincheng <1481155734@qq.com>");
MODULE_DESCRIPTION("GPIO W1 driver on i.mx6ull platform");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:w1-dev");
